// ====================================================================
// ConstantInt
// ====================================================================

///| ConstantInt
///
/// Use `Context::getConstInt8, getConstInt16, getConstInt32, getConstInt64`
/// To create a new ConstantInt.
///
/// ```moonbit
/// let ctx = Context::new()
/// let i8 = ctx.getConstInt8(0)
/// let i16 = ctx.getConstInt16(1)
/// let i32 = ctx.getConstInt32(-2)
/// let i64 = ctx.getConstInt64(16)
///
/// inspect(i8, content="i8 0")
/// inspect(i16, content="i16 1")
/// inspect(i32, content="i32 -2")
/// inspect(i64, content="i64 16")
/// ```
pub struct ConstantInt {
  // unique identifier for this value
  uid : UInt64

  // Value Type
  vty : &IntegerType

  // the value of this constant
  value : Int64
} derive(Eq)

///|
fn ConstantInt::new(vty : &IntegerType, value : Int64) -> ConstantInt {
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => if value == 0 { 0L } else { 1L }
    Int8Type(_) => Int8::from_int64(value).to_int64()
    Int16Type(_) => value.to_int().to_int16().to_int().to_int64()
    Int32Type(_) => value.to_int().to_int64()
    Int64Type(_) => value
  }
  let uid = valueUIDAssigner.assign()
  ConstantInt::{ uid, vty, value }
}

///|
fn ConstantInt::getValue(self : ConstantInt) -> Int64 {
  self.value
}

///| Get the value of this constant as a signed 64-bits integer.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_zero = ctx.getConstInt8(0)
/// let i16_one = ctx.getConstInt16(1)
/// let i32_two = ctx.getConstInt32(2)
/// let i64_m1 = ctx.getConstInt64(-1)
/// let i8_m1 = ctx.getConstInt8(-1)
///
/// assert_eq(i8_zero.getValueAsInt64(), 0)
/// assert_eq(i16_one.getValueAsInt64(), 1)
/// assert_eq(i32_two.getValueAsInt64(), 2)
/// assert_eq(i64_m1.getValueAsInt64(), -1)
/// assert_eq(i8_m1.getValueAsInt64(), -1)
/// ```
pub fn ConstantInt::getValueAsInt64(self : ConstantInt) -> Int64 {
  self.value
}

///| Get the type of constant int value.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_zero = ctx.getConstInt8(0)
/// let i16_one = ctx.getConstInt16(1)
/// let i32_m2 = ctx.getConstInt32(-2)
/// let i64_m16 = ctx.getConstInt64(-16)
///
/// inspect(i8_zero.getIntegerType(), content="i8")
/// inspect(i16_one.getIntegerType(), content="i16")
/// inspect(i32_m2.getIntegerType(), content="i32")
/// inspect(i64_m16.getIntegerType(), content="i64")
/// ```
pub fn ConstantInt::getIntegerType(self : ConstantInt) -> &IntegerType {
  self.vty
}

///| Compare the value of this constant int with a signed integer.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// assert_true(ctx.getConstInt8(0).equals(0))
/// assert_true(ctx.getConstInt8(-1).equals(-1))
/// assert_true(ctx.getConstInt8(1).equals(1))
/// assert_true(ctx.getConstInt16(16).equals(16))
/// assert_true(ctx.getConstInt32(-128).equals(-128))
/// ```
pub fn[T : IntegerNumber] ConstantInt::equals(
  self : ConstantInt,
  value : T,
) -> Bool {
  self.value == value.convert_to_int64()
}

///|
pub fn ConstantInt::isNegative(self : ConstantInt) -> Bool {
  self.value < 0
}

///|
pub fn ConstantInt::isMaxValue(self : ConstantInt) -> Bool {
  guard self.getType().tryAsIntTypeEnum() is Some(ty)
  match ty {
    Int1Type(_) => self.value == 1
    Int8Type(_) => Int8::from_int64(self.value) == @int8.max_value
    Int16Type(_) => self.value.to_int().to_int16() == @int16.max_value
    Int32Type(_) => self.value.to_int() == @int.max_value
    Int64Type(_) => self.value == @int64.max_value
  }
}

///|
pub fn ConstantInt::isMinValue(self : ConstantInt) -> Bool {
  guard self.getType().tryAsIntTypeEnum() is Some(ty)
  match ty {
    Int1Type(_) => self.value == 0
    Int8Type(_) => Int8::from_int64(self.value) == @int8.min_value
    Int16Type(_) => self.value.to_int().to_int16() == @int16.min_value
    Int32Type(_) => self.value.to_int() == @int.min_value
    Int64Type(_) => self.value == @int64.min_value
  }
}

///| Add two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_0 = ctx.getConstInt8(0)
/// let i8_31 = ctx.getConstInt8(31)
/// inspect(i8_0.add(i8_31), content="i8 31")
///
/// let i16_5 = ctx.getConstInt16(5)
/// let i16_72 = ctx.getConstInt16(72)
/// inspect(i16_5.add(i16_72), content="i16 77")
///
/// let i32_m7 = ctx.getConstInt32(-7)
/// let i32_81 = ctx.getConstInt32(81)
/// inspect(i32_m7.add(i32_81), content="i32 74")
///
/// let i64_16 = ctx.getConstInt64(16)
/// let i64_m33 = ctx.getConstInt64(-33)
/// inspect(i64_16.add(i64_m33), content="i64 -17")
///
/// assert_true({try? i8_0.add(i16_5)} is Err(_))
/// ```
pub fn ConstantInt::add(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt addition: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let value = self.value + other.value
  let vty = lty
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => value & 1L
    Int8Type(_) => Int8::from_int64(value).to_int64()
    Int16Type(_) => value.to_int().to_int16().to_int64()
    Int32Type(_) => value.to_int().to_int64()
    Int64Type(_) => value
  }
  ConstantInt::new(vty, value)
}

///| Subtract two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_0 = ctx.getConstInt8(0)
/// let i8_31 = ctx.getConstInt8(31)
/// inspect(i8_31.sub(i8_0), content="i8 31")
/// inspect(i8_0.sub(i8_31), content="i8 -31")
///
/// let i16_5 = ctx.getConstInt16(5)
/// let i16_72 = ctx.getConstInt16(72)
/// inspect(i16_72.sub(i16_5), content="i16 67")
///
/// let i32_m7 = ctx.getConstInt32(-7)
/// let i32_81 = ctx.getConstInt32(81)
/// inspect(i32_m7.sub(i32_81), content="i32 -88")
///
/// let i64_16 = ctx.getConstInt64(16)
/// let i64_m33 = ctx.getConstInt64(-33)
/// inspect(i64_16.sub(i64_m33), content="i64 49")
///
/// assert_true({try? i32_81.sub(i64_16)} is Err(_))
/// ```
pub fn ConstantInt::sub(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt subtraction: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value - other.value
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => value & 1L
    Int8Type(_) => Int8::from_int64(value).to_int64()
    Int16Type(_) => value.to_int().to_int16().to_int64()
    Int32Type(_) => value.to_int().to_int64()
    Int64Type(_) => value
  }
  ConstantInt::new(vty, value)
}

///| Multiply two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_0 = ctx.getConstInt8(0)
/// let i8_31 = ctx.getConstInt8(31)
/// inspect(i8_0.mul(i8_31), content="i8 0")
///
/// let i16_5 = ctx.getConstInt16(5)
/// let i16_72 = ctx.getConstInt16(72)
/// inspect(i16_5.mul(i16_72), content="i16 360")
///
/// let i32_m7 = ctx.getConstInt32(-7)
/// let i32_81 = ctx.getConstInt32(81)
/// inspect(i32_m7.mul(i32_81), content="i32 -567")
///
/// let i64_16 = ctx.getConstInt64(16)
/// let i64_m33 = ctx.getConstInt64(-33)
/// inspect(i64_16.mul(i64_m33), content="i64 -528")
/// ```
pub fn ConstantInt::mul(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt multiplication: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value * other.value
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => value & 1L
    Int8Type(_) => Int8::from_int64(value).to_int64()
    Int16Type(_) => value.to_int().to_int16().to_int64()
    Int32Type(_) => value.to_int().to_int64()
    Int64Type(_) => value
  }
  ConstantInt::new(vty, value)
}

///| Divide two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_1 = ctx.getConstInt8(1)
/// let i8_31 = ctx.getConstInt8(31)
/// inspect(i8_31.sdiv(i8_1), content="i8 31")
///
/// let i16_5 = ctx.getConstInt16(5)
/// let i16_72 = ctx.getConstInt16(72)
/// inspect(i16_72.sdiv(i16_5), content="i16 14")
///
/// let i32_m7 = ctx.getConstInt32(-7)
/// let i32_81 = ctx.getConstInt32(81)
/// inspect(i32_m7.sdiv(i32_81), content="i32 0")
///
/// let i64_16 = ctx.getConstInt64(16)
/// let i64_m33 = ctx.getConstInt64(-33)
/// inspect(i64_16.sdiv(i64_m33), content="i64 0")
/// ```
pub fn ConstantInt::sdiv(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt signed division: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  guard other.value != 0 else {
    let msg = "Division by zero in ConstantInt signed division"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value / other.value
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => value & 1L
    Int8Type(_) => Int8::from_int64(value).to_int64()
    Int16Type(_) => value.to_int().to_int16().to_int64()
    Int32Type(_) => value.to_int().to_int64()
    Int64Type(_) => value
  }
  ConstantInt::new(vty, value)
}

///| Unsigned divide two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same. It will raise `DivisionByZeroError` if `other` is zero.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test case 1: i8 unsigned division
/// // 200u8 / 10u8 = 20u8. ConstantInt stores 20L.
/// let i8_200 = ctx.getConstInt8(200) // Internally stored as -56L
/// let i8_10 = ctx.getConstInt8(10)
/// inspect(i8_200.udiv(i8_10), content="i8 20")
///
/// // Test case 2: i8 unsigned division with -1 (255u8)
/// // 255u8 / 2u8 = 127u8. ConstantInt stores 127L.
/// let i8_m1 = ctx.getConstInt8(-1) // Internally stored as -1L, represents 255u8
/// let i8_2 = ctx.getConstInt8(2)
/// inspect(i8_m1.udiv(i8_2), content="i8 127")
///
/// // Test case 3: i32 unsigned division
/// // 2147483647u / 2u = 1073741823u.
/// let i32_max_signed = ctx.getConstInt32(@int.max_value) // 2147483647
/// let i32_2 = ctx.getConstInt32(2)
/// inspect(i32_max_signed.udiv(i32_2), content="i32 1073741823")
///
/// // Test case 4: i64 unsigned division (UInt64.max_value / 2)
/// // (-1L as UInt64) / 2 = (2^64 - 1) / 2 = 2^63 - 1 (which is Int64.max_value)
/// let i64_m1 = ctx.getConstInt64(-1L)
/// let i64_2 = ctx.getConstInt64(2L)
/// inspect(i64_m1.udiv(i64_2), content="i64 9223372036854775807")
///
/// // Test case 5: Type mismatch
/// let i8_5 = ctx.getConstInt8(5)
/// let i16_2 = ctx.getConstInt16(2)
/// assert_true({try? i8_5.udiv(i16_2)} is Err(_))
///
/// // Test case 6: Division by zero
/// let i8_0 = ctx.getConstInt8(0)
/// assert_true({try? i8_5.udiv(i8_0)} is Err(_))
///
/// // Test case 7: i1 unsigned division
/// let i1_true = ctx.getConstTrue()
/// let i1_false = ctx.getConstFalse()
/// inspect(i1_true.udiv(i1_true), content="i1 true") // 1u / 1u = 1u
/// inspect(i1_false.udiv(i1_true), content="i1 false") // 0u / 1u = 0u
/// ```
pub fn ConstantInt::udiv(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt unsigned division: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  guard other.value != 0L else {
    let msg = "Division by zero in ConstantInt unsigned division"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => self.value / other.value
    Int8Type(_) => {
      let v1_u8 = self.value.to_byte() // e.g., -56L (representing 200i8) -> 200uy (0xC8)
      let v2_u8 = other.value.to_byte() // e.g., 10L (representing 10i8) -> 10uy (0x0A)
      let res_u8 = v1_u8 / v2_u8 // 200uy / 10uy = 20uy (0x14)
      Int8::from_int64(res_u8.to_int64()).to_int64()
    }
    Int16Type(_) => {
      let v1_u16 = self.value.to_int().to_uint16()
      let v2_u16 = other.value.to_int().to_uint16()
      let res_u16 = v1_u16 / v2_u16
      res_u16.to_int().to_int16().to_int().to_int64()
    }
    Int32Type(_) => {
      let v1_u32 = self.value.to_int().reinterpret_as_uint()
      let v2_u32 = other.value.to_int().reinterpret_as_uint()
      let res_u32 = v1_u32 / v2_u32
      res_u32.reinterpret_as_int().to_int64()
    }
    Int64Type(_) =>
      (self.value.reinterpret_as_uint64() / other.value.reinterpret_as_uint64()).reinterpret_as_int64()
  }
  ConstantInt::new(vty, value)
}

///| Bitwise AND two ConstantInt, useful in constant folding.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_6 = ctx.getConstInt8(6)     // 00000110
/// let i8_3 = ctx.getConstInt8(3)     // 00000011
/// inspect(i8_6.compute_and(i8_3), content="i8 2") // 00000010
///
/// let i16_m1 = ctx.getConstInt16(-1) // 1111111111111111
/// let i16_all_set = ctx.getConstInt16(-1)
/// inspect(i16_m1.compute_and(i16_all_set), content="i16 -1")
///
/// let i32_pattern = ctx.getConstInt32(0x0F0F0F0F)
/// let i32_mask = ctx.getConstInt32(0xFF00FF00)
/// inspect(i32_pattern.compute_and(i32_mask), content="i32 251662080") // 0x0F000F00
///
/// let i64_val = ctx.getConstInt64(0x123456789ABCDEF0L)
/// let i64_low_clear = ctx.getConstInt64(-1L<<4) // ...FFFFFFFFFFFFFFF0
/// inspect(i64_val.compute_and(i64_low_clear), content="i64 1311768467463790320") // 0x123456789ABCDEF0
/// ```
pub fn ConstantInt::compute_and(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt bitwise AND: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let result_i64 = self.value & other.value
  let final_value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => result_i64 & 1L
    Int8Type(_) => Int8::from_int64(result_i64).to_int64()
    Int16Type(_) => result_i64.to_int().to_int16().to_int64()
    Int32Type(_) => result_i64.to_int().to_int64()
    Int64Type(_) => result_i64
  }
  ConstantInt::new(vty, final_value)
}

///| Bitwise OR two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_6 = ctx.getConstInt8(6)     // 00000110
/// let i8_3 = ctx.getConstInt8(3)     // 00000011
/// inspect(i8_6.or(i8_3), content="i8 7")  // 00000111
///
/// let i16_1 = ctx.getConstInt16(1)   // ...0001
/// let i16_2 = ctx.getConstInt16(2)   // ...0010
/// inspect(i16_1.or(i16_2), content="i16 3") // ...0011
///
/// let i32_pattern = ctx.getConstInt32(0x0F0F0F0F)
/// let i32_mask = ctx.getConstInt32(0xF0F0F0F0)
/// inspect(i32_pattern.or(i32_mask), content="i32 -1") // 0xFFFFFFFF
///
/// let i64_val = ctx.getConstInt64(0L)
/// let i64_m1 = ctx.getConstInt64(-1L)
/// inspect(i64_val.or(i64_m1), content="i64 -1")
/// ```
pub fn ConstantInt::or(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt bitwise OR: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let result_i64 = self.value | other.value
  let final_value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => result_i64 & 1L
    Int8Type(_) => Int8::from_int64(result_i64).to_int64()
    Int16Type(_) => result_i64.to_int().to_int16().to_int64()
    Int32Type(_) => result_i64.to_int().to_int64()
    Int64Type(_) => result_i64
  }
  ConstantInt::new(vty, final_value)
}

///| Bitwise XOR two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_6 = ctx.getConstInt8(6)     // 00000110
/// let i8_3 = ctx.getConstInt8(3)     // 00000011
/// inspect(i8_6.xor(i8_3), content="i8 5")  // 00000101
///
/// let i16_val = ctx.getConstInt16(0xAA) // ...10101010
/// let i16_m1 = ctx.getConstInt16(-1)   // ...11111111
/// inspect(i16_val.xor(i16_m1), content="i16 -171") // ...01010101 (which is -0xAB or -171 for i16)
///
/// let i32_pattern = ctx.getConstInt32(0x0F0F0F0F)
/// let i32_self_xor = i32_pattern.xor(i32_pattern)
/// inspect(i32_self_xor, content="i32 0")
///
/// let i64_val = ctx.getConstInt64(12345L)
/// let i64_0 = ctx.getConstInt64(0L)
/// inspect(i64_val.xor(i64_0), content="i64 12345")
/// ```
pub fn ConstantInt::xor(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt bitwise XOR: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let result_i64 = self.value ^ other.value
  let final_value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => result_i64 & 1L
    Int8Type(_) => Int8::from_int64(result_i64).to_int64()
    Int16Type(_) => result_i64.to_int().to_int16().to_int64()
    Int32Type(_) => result_i64.to_int().to_int64()
    Int64Type(_) => result_i64
  }
  ConstantInt::new(vty, final_value)
}

///| Left shift (shl) two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_1 = ctx.getConstInt8(1)     // 00000001
/// let i8_3 = ctx.getConstInt8(3)     // shift by 3
/// inspect(i8_1.compute_shl(i8_3), content="i8 8")  // 00001000
///
/// let i16_5 = ctx.getConstInt16(5)   // ...00000101
/// let i16_2 = ctx.getConstInt16(2)   // shift by 2
/// inspect(i16_5.compute_shl(i16_2), content="i16 20") // ...00010100
///
/// let i32_7 = ctx.getConstInt32(7)
/// let i32_4 = ctx.getConstInt32(4)
/// inspect(i32_7.compute_shl(i32_4), content="i32 112") // 7 << 4 = 112
///
/// let i64_val = ctx.getConstInt64(0x123L)
/// let i64_8 = ctx.getConstInt64(8L)
/// inspect(i64_val.compute_shl(i64_8), content="i64 74496") // 0x123 << 8 = 0x12300
/// ```
pub fn ConstantInt::compute_shl(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt left shift: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let result_i64 = self.value << other.value.to_int()
  let final_value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => result_i64 & 1L
    Int8Type(_) => Int8::from_int64(result_i64).to_int64()
    Int16Type(_) => result_i64.to_int().to_int16().to_int64()
    Int32Type(_) => result_i64.to_int().to_int64()
    Int64Type(_) => result_i64
  }
  ConstantInt::new(vty, final_value)
}

///| Logical right shift (lshr) two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_200 = ctx.getConstInt8(200)  // 11001000 (as unsigned)
/// let i8_2 = ctx.getConstInt8(2)      // shift by 2
/// inspect(i8_200.lshr(i8_2), content="i8 50")  // 00110010 (50)
///
/// let i16_val = ctx.getConstInt16(-32768) // 1000000000000000
/// let i16_1 = ctx.getConstInt16(1)         // shift by 1
/// inspect(i16_val.lshr(i16_1), content="i16 16384") // 0100000000000000 (16384)
///
/// let i32_m1 = ctx.getConstInt32(-1)  // 0xFFFFFFFF
/// let i32_4 = ctx.getConstInt32(4)    // shift by 4
/// inspect(i32_m1.lshr(i32_4), content="i32 268435455") // 0x0FFFFFFF
///
/// let i64_val = ctx.getConstInt64(0x8000000000000000L) // MSB set
/// let i64_1 = ctx.getConstInt64(1L)
/// inspect(i64_val.lshr(i64_1), content="i64 4611686018427387904") // logical shift
/// ```
pub fn ConstantInt::lshr(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt logical right shift: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => (self.value >> other.value.to_int()) & 1L
    Int8Type(_) => {
      let v_u8 = self.value.to_byte()
      let shift_amount = other.value.to_int()
      let res_u8 = v_u8 >> shift_amount
      Int8::from_int64(res_u8.to_int64()).to_int64()
    }
    Int16Type(_) => {
      let v_u16 = self.value.to_int().to_uint16()
      let shift_amount = other.value.to_int()
      let res_u16 = v_u16 >> shift_amount
      res_u16.to_int().to_int16().to_int().to_int64()
    }
    Int32Type(_) => {
      let v_u32 = self.value.to_int().reinterpret_as_uint()
      let shift_amount = other.value.to_int()
      let res_u32 = v_u32 >> shift_amount
      res_u32.reinterpret_as_int().to_int64()
    }
    Int64Type(_) => {
      let v_u64 = self.value.reinterpret_as_uint64()
      let shift_amount = other.value.to_int()
      (v_u64 >> shift_amount).reinterpret_as_int64()
    }
  }
  ConstantInt::new(vty, value)
}

///| Arithmetic right shift (ashr) two ConstantInt, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_m1 = ctx.getConstInt8(-1)   // 11111111
/// let i8_2 = ctx.getConstInt8(2)     // shift by 2
/// inspect(i8_m1.ashr(i8_2), content="i8 -1")  // 11111111 (sign extended)
///
/// let i16_m16 = ctx.getConstInt16(-16) // ...1111111111110000
/// let i16_2 = ctx.getConstInt16(2)      // shift by 2
/// inspect(i16_m16.ashr(i16_2), content="i16 -4") // ...1111111111111100
///
/// let i32_m8 = ctx.getConstInt32(-8)
/// let i32_1 = ctx.getConstInt32(1)
/// inspect(i32_m8.ashr(i32_1), content="i32 -4") // -8 >> 1 = -4 (arithmetic)
///
/// let i64_m128 = ctx.getConstInt64(-128L)
/// let i64_3 = ctx.getConstInt64(3L)
/// inspect(i64_m128.ashr(i64_3), content="i64 -16") // -128 >> 3 = -16
///
/// // Positive numbers should behave like logical shift
/// let i32_64 = ctx.getConstInt32(64)
/// let i32_2 = ctx.getConstInt32(2)
/// inspect(i32_64.ashr(i32_2), content="i32 16") // 64 >> 2 = 16
/// ```
pub fn ConstantInt::ashr(
  self : ConstantInt,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt arithmetic right shift: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let result_i64 = self.value >> other.value.to_int()
  let final_value = match vty.asIntegerTypeEnum() {
    Int1Type(_) => result_i64 & 1L
    Int8Type(_) => Int8::from_int64(result_i64).to_int64()
    Int16Type(_) => result_i64.to_int().to_int16().to_int64()
    Int32Type(_) => result_i64.to_int().to_int64()
    Int64Type(_) => result_i64
  }
  ConstantInt::new(vty, final_value)
}

///| Compare two ConstantInt using the given predicate, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantInt are not the same. Only ICMP_xxx predicates are supported.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_5 = ctx.getConstInt8(5)
/// let i8_10 = ctx.getConstInt8(10)
/// let i8_m5 = ctx.getConstInt8(-5)
///
/// // Test EQ
/// inspect(i8_5.compare(EQ, i8_5), content="i1 true")
/// inspect(i8_5.compare(EQ, i8_10), content="i1 false")
///
/// // Test NE
/// inspect(i8_5.compare(NE, i8_10), content="i1 true")
/// inspect(i8_5.compare(NE, i8_5), content="i1 false")
///
/// // Test SGT (signed greater than)
/// inspect(i8_10.compare(SGT, i8_5), content="i1 true")
/// inspect(i8_5.compare(SGT, i8_10), content="i1 false")
/// inspect(i8_5.compare(SGT, i8_m5), content="i1 true")
///
/// // Test UGT (unsigned greater than)
/// let i8_200 = ctx.getConstInt8(200) // -56 as signed, 200 as unsigned
/// inspect(i8_200.compare(UGT, i8_10), content="i1 true")
/// inspect(i8_10.compare(UGT, i8_200), content="i1 false")
///
/// // Test SLT (signed less than)
/// inspect(i8_5.compare(SLT, i8_10), content="i1 true")
/// inspect(i8_m5.compare(SLT, i8_5), content="i1 true")
///
/// // Test ULE (unsigned less or equal)
/// inspect(i8_5.compare(ULE, i8_10), content="i1 true")
/// inspect(i8_5.compare(ULE, i8_5), content="i1 true")
///
/// // Test type mismatch
/// let i16_5 = ctx.getConstInt16(5)
/// assert_true({try? i8_5.compare(EQ, i16_5)} is Err(_))
/// ```
pub fn ConstantInt::compare(
  self : ConstantInt,
  predicate : IntPredicate,
  other : ConstantInt,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getIntegerType(), other.getIntegerType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantInt comparison: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let ctx = self.getType().getContext()
  let result = match predicate {
    EQ => self.value == other.value
    NE => self.value != other.value
    SGT => self.value > other.value
    SGE => self.value >= other.value
    SLT => self.value < other.value
    SLE => self.value <= other.value
    UGT =>
      match vty.asIntegerTypeEnum() {
        Int1Type(_) => self.value > other.value
        Int8Type(_) => self.value.to_byte() > other.value.to_byte()
        Int16Type(_) =>
          self.value.to_int().to_uint16() > other.value.to_int().to_uint16()
        Int32Type(_) =>
          self.value.to_int().reinterpret_as_uint() >
          other.value.to_int().reinterpret_as_uint()
        Int64Type(_) =>
          self.value.reinterpret_as_uint64() >
          other.value.reinterpret_as_uint64()
      }
    UGE =>
      match vty.asIntegerTypeEnum() {
        Int1Type(_) => self.value >= other.value
        Int8Type(_) => self.value.to_byte() >= other.value.to_byte()
        Int16Type(_) =>
          self.value.to_int().to_uint16() >= other.value.to_int().to_uint16()
        Int32Type(_) =>
          self.value.to_int().reinterpret_as_uint() >=
          other.value.to_int().reinterpret_as_uint()
        Int64Type(_) =>
          self.value.reinterpret_as_uint64() >=
          other.value.reinterpret_as_uint64()
      }
    ULT =>
      match vty.asIntegerTypeEnum() {
        Int1Type(_) => self.value < other.value
        Int8Type(_) => self.value.to_byte() < other.value.to_byte()
        Int16Type(_) =>
          self.value.to_int().to_uint16() < other.value.to_int().to_uint16()
        Int32Type(_) =>
          self.value.to_int().reinterpret_as_uint() <
          other.value.to_int().reinterpret_as_uint()
        Int64Type(_) =>
          self.value.reinterpret_as_uint64() <
          other.value.reinterpret_as_uint64()
      }
    ULE =>
      match vty.asIntegerTypeEnum() {
        Int1Type(_) => self.value <= other.value
        Int8Type(_) => self.value.to_byte() <= other.value.to_byte()
        Int16Type(_) =>
          self.value.to_int().to_uint16() <= other.value.to_int().to_uint16()
        Int32Type(_) =>
          self.value.to_int().reinterpret_as_uint() <=
          other.value.to_int().reinterpret_as_uint()
        Int64Type(_) =>
          self.value.reinterpret_as_uint64() <=
          other.value.reinterpret_as_uint64()
      }
  }
  match result {
    true => ctx.getConstTrue()
    false => ctx.getConstFalse()
  }
}

///| Truncate this ConstantInt to a smaller integer type, useful in constant folding.
///
/// **Note**: It will raise `TruncCastInstTypeMismatch` if the source type bit width
/// is not greater than the destination type bit width.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i32_255 = ctx.getConstInt32(255)
/// let i8_255 = i32_255.trunc(ctx.getInt8Ty())
/// inspect(i8_255, content="i8 -1")
///
/// let i64_0xFFFF = ctx.getConstInt64(0xFFFF)
/// let i16_0xFFFF = i64_0xFFFF.trunc(ctx.getInt16Ty())
/// inspect(i16_0xFFFF, content="i16 -1")
///
/// let i32_12345 = ctx.getConstInt32(12345)
/// let i16_12345 = i32_12345.trunc(ctx.getInt16Ty())
/// inspect(i16_12345, content="i16 12345")
///
/// // Test error case: cannot truncate to larger type
/// let i8_val = ctx.getConstInt8(42)
/// assert_true({try? i8_val.trunc(ctx.getInt32Ty())} is Err(_))
/// ```
pub fn ConstantInt::trunc(
  self : ConstantInt,
  dst_ty : &IntegerType,
) -> ConstantInt raise LLVMValueError {
  let src_ty = self.vty.asIntegerTypeEnum()
  guard src_ty.getBitWidth() > dst_ty.getBitWidth() else {
    let msg = "Truncation cast type mismatch: cannot truncate from \{src_ty.asTypeClass()} (bit width \{src_ty.getBitWidth()}) to \{dst_ty} (bit width \{dst_ty.getBitWidth()})"
    raise LLVMValueError(msg)
  }
  ConstantInt::new(dst_ty, self.value)
}

///| Zero extend this ConstantInt to a larger integer type, useful in constant folding.
///
/// **Note**: It will raise `ZExtCastInstTypeMismatch` if the source type bit width
/// is not less than the destination type bit width.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_255 = ctx.getConstInt8(255) // -1 as signed, 255 as unsigned
/// let i32_255 = i8_255.zext(ctx.getInt32Ty())
/// inspect(i32_255, content="i32 255")
///
/// let i1_true = ctx.getConstTrue()
/// let i8_true = i1_true.zext(ctx.getInt8Ty())
/// inspect(i8_true, content="i8 1")
///
/// let i16_42 = ctx.getConstInt16(42)
/// let i64_42 = i16_42.zext(ctx.getInt64Ty())
/// inspect(i64_42, content="i64 42")
///
/// // Test error case: cannot zext to smaller type
/// let i32_val = ctx.getConstInt32(42)
/// assert_true({try? i32_val.zext(ctx.getInt8Ty())} is Err(_))
/// ```
pub fn ConstantInt::zext(
  self : ConstantInt,
  dst_ty : &IntegerType,
) -> ConstantInt raise LLVMValueError {
  let src_ty = self.vty.asIntegerTypeEnum()
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Zero extension cast type mismatch: cannot zero extend from \{src_ty.asTypeClass()} (bit width \{src_ty.getBitWidth()}) to \{dst_ty} (bit width \{dst_ty.getBitWidth()})"
    raise LLVMValueError(msg)
  }
  // Zero extension: treat the value as unsigned
  let unsigned_value = match src_ty {
    Int1Type(_) => self.value.reinterpret_as_uint64()
    Int8Type(_) => self.value.to_byte().to_uint64()
    Int16Type(_) => self.value.to_int().to_uint16().to_int().to_uint64()
    Int32Type(_) => self.value.to_int().reinterpret_as_uint().to_uint64()
    Int64Type(_) => self.value.reinterpret_as_uint64()
  }
  ConstantInt::new(dst_ty, unsigned_value.reinterpret_as_int64())
}

///| Sign extend this ConstantInt to a larger integer type, useful in constant folding.
///
/// **Note**: It will raise `SExtCastInstTypeMismatch` if the source type bit width
/// is not less than the destination type bit width.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i8_m1 = ctx.getConstInt8(-1)
/// let i32_m1 = i8_m1.sext(ctx.getInt32Ty())
/// inspect(i32_m1, content="i32 -1")
///
/// let i1_true = ctx.getConstTrue()
/// let i8_true = i1_true.sext(ctx.getInt8Ty())
/// inspect(i8_true, content="i8 -1")
///
/// let i16_42 = ctx.getConstInt16(42)
/// let i64_42 = i16_42.sext(ctx.getInt64Ty())
/// inspect(i64_42, content="i64 42")
///
/// // Test error case: cannot sext to smaller type
/// let i32_val = ctx.getConstInt32(42)
/// assert_true({try? i32_val.sext(ctx.getInt8Ty())} is Err(_))
/// ```
pub fn ConstantInt::sext(
  self : ConstantInt,
  dst_ty : &IntegerType,
) -> ConstantInt raise LLVMValueError {
  let src_ty = self.vty.asIntegerTypeEnum()
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Sign extension cast type mismatch: cannot sign extend from \{src_ty.asTypeClass()} (bit width \{src_ty.getBitWidth()}) to \{dst_ty} (bit width \{dst_ty.getBitWidth()})"
    raise LLVMValueError(msg)
  }
  // Sign extension: need special handling for i1
  let extended_value = match src_ty {
    Int1Type(_) => if self.value != 0 { -1L } else { 0L }
    _ => self.value // Other types are already properly sign-extended in Int64
  }
  ConstantInt::new(dst_ty, extended_value)
}

///| Convert this ConstantInt to a floating-point type (unsigned interpretation), useful in constant folding.
///
/// **Note**: It will raise `UIToFPInstTypeMismatch` if the source is not an integer type.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i32_255 = ctx.getConstInt32(255)
/// let f32_255 = i32_255.uitofp(ctx.getFloatTy())
/// assert_eq(f32_255.getValue(), 255.0)
///
/// let i8_m1 = ctx.getConstInt8(-1) // 255 as unsigned
/// let f64_255 = i8_m1.uitofp(ctx.getDoubleTy())
/// assert_eq(f64_255.getValue(), 255.0)
///
/// let i64_0 = ctx.getConstInt64(0)
/// let f32_0 = i64_0.uitofp(ctx.getFloatTy())
/// assert_eq(f32_0.getValue(), 0.0)
/// ```
pub fn ConstantInt::uitofp(self : ConstantInt, dst_ty : &FPType) -> ConstantFP {
  let src_ty = self.vty.asIntegerTypeEnum()
  // Convert unsigned integer to floating point
  let unsigned_value = match src_ty {
    Int1Type(_) => self.value.reinterpret_as_uint64()
    Int8Type(_) => self.value.to_byte().to_uint64()
    Int16Type(_) => self.value.to_int().to_uint16().to_int().to_uint64()
    Int32Type(_) => self.value.to_int().reinterpret_as_uint().to_uint64()
    Int64Type(_) => self.value.reinterpret_as_uint64()
  }
  let fp_value = unsigned_value.to_double()
  ConstantFP::new(dst_ty, fp_value)
}

///| Convert this ConstantInt to a floating-point type (signed interpretation), useful in constant folding.
///
/// **Note**: It will raise `SIToFPInstTypeMismatch` if the source is not an integer type.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i32_m42 = ctx.getConstInt32(-42)
/// let f32_m42 = i32_m42.sitofp(ctx.getFloatTy())
/// assert_eq(f32_m42.getValue(), -42.0)
///
/// let i8_127 = ctx.getConstInt8(127)
/// let f64_127 = i8_127.sitofp(ctx.getDoubleTy())
/// assert_eq(f64_127.getValue(), 127.0)
///
/// let i64_0 = ctx.getConstInt64(0)
/// let f32_0 = i64_0.sitofp(ctx.getFloatTy())
/// assert_eq(f32_0.getValue(), 0.0)
/// ```
pub fn ConstantInt::sitofp(self : ConstantInt, dst_ty : &FPType) -> ConstantFP {
  // Convert signed integer to floating point
  let fp_value = self.value.to_double()
  ConstantFP::new(dst_ty, fp_value)
}

///| Convert this ConstantInt to a pointer type, useful in constant folding.
///
/// **Note**: It will raise `IntToPtrCastInstTypeMismatch` if the source is not an integer type.
/// For constant folding, this always returns a null pointer since we cannot create
/// meaningful pointer constants from integer values at compile time.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let i64_0 = ctx.getConstInt64(0)
/// let null_ptr = i64_0.inttoptr()
/// inspect(null_ptr, content="ptr null")
///
/// let i32_42 = ctx.getConstInt32(42)
/// let ptr_42 = i32_42.inttoptr()
/// inspect(ptr_42, content="ptr null") // Still null in constant folding
/// ```
pub fn ConstantInt::inttoptr(self : ConstantInt) -> ConstantPointerNull {
  let ctx = self.getType().getContext()
  let ptr_ty = ctx.getPtrTy()
  ConstantPointerNull::new(ptr_ty)
}

///| Bitcast this ConstantInt to another primitive type of the same bit width, useful in constant folding.
///
/// **Note**: It will raise `BitCastInstTypeMismatch` if the types have different bit widths
/// or if the cast is invalid. It will raise `BitCastOnlyAcceptPrimitiveTypes` if the source
/// is not a primitive type.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test i32 to f32 bitcast
/// let i32_val = ctx.getConstInt32(0x40000000) // 2.0 in float
/// let f32_val = i32_val.bitcast(ctx.getFloatTy())
/// guard f32_val.asConstantEnum() is ConstantFP(f32_val)
/// assert_eq(f32_val.getValue(), 2.0)
///
/// // Test i64 to f64 bitcast
/// let i64_val = ctx.getConstInt64(0x4000000000000000L) // 2.0 in double
/// let f64_val = i64_val.bitcast(ctx.getDoubleTy())
/// guard f64_val.asConstantEnum() is ConstantFP(f64_val)
/// assert_eq(f64_val.getValue(), 2.0)
///
/// // Test error case: different bit widths
/// let i8_val = ctx.getConstInt8(42)
/// assert_true({try? i8_val.bitcast(ctx.getFloatTy())} is Err(_))
/// ```
pub fn ConstantInt::bitcast(
  self : ConstantInt,
  dst_ty : &PrimitiveType,
) -> &Constant raise LLVMValueError {
  let src_ty = self.getType()
  guard src_ty != dst_ty else {
    let msg = "Bitcast type mismatch: source type \{src_ty} is the same as destination type \{dst_ty}"
    raise LLVMValueError(msg)
  }
  guard src_ty.tryAsPrimitiveTypeEnum() is Some(src_ty_prim) else {
    let msg = "Bitcast only accepts primitive types: source type \{src_ty} is not a primitive type"
    raise LLVMValueError(msg)
  }
  guard src_ty_prim.getBitWidth() == dst_ty.getBitWidth() else {
    let msg = "Bitcast type mismatch: source type \{src_ty} (bit width \{src_ty_prim.getBitWidth()}) and destination type \{dst_ty} (bit width \{dst_ty.getBitWidth()}) have different bit widths"
    raise LLVMValueError(msg)
  }
  match dst_ty.asPrimitiveTypeEnum() {
    // Integer to integer
    Int1Type(_) | Int8Type(_) | Int16Type(_) | Int32Type(_) | Int64Type(_) =>
      ConstantInt::new(dst_ty.tryAsIntType().unwrap(), self.value) as &Constant
    // Integer to floating point
    FloatType(dst_ty) => {
      // For i32 to f32, we need to treat the 32-bit value as float bits
      let i32_bits = self.value.to_int().reinterpret_as_uint()
      let f32_value = i32_bits.reinterpret_as_float().to_double()
      ConstantFP::new(dst_ty, f32_value)
    }
    DoubleType(dst_ty) => {
      let fp_bits = self.value.reinterpret_as_uint64()
      let fp_value = fp_bits.reinterpret_as_double()
      ConstantFP::new(dst_ty, fp_value)
    }
    HalfType(_) | BFloatType(_) | FP128Type(_) => {
      // For other FP types, we also interpret as double for now
      let fp_bits = self.value.reinterpret_as_uint64()
      let fp_value = fp_bits.reinterpret_as_double()
      ConstantFP::new(dst_ty.tryAsFPType().unwrap(), fp_value)
    }
  }
}

///|
pub impl Value for ConstantInt with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantInt with asValueEnum(self) {
  ConstantInt(self)
}

///|
pub impl Value for ConstantInt with getValueRepr(self) {
  let vty = self.getIntegerType()
  let val = match vty.asIntegerTypeEnum() {
    Int1Type(_) => if self.value != 0 { "true" } else { "false" }
    _ => self.value.to_string()
  }
  "\{val}"
}

///|
pub impl Value for ConstantInt with getName(_) {
  None
}

///|
pub impl Value for ConstantInt with setName(_, _) {
  let msg = "Calling always failed function `ConstantInt::setName`. " +
    "Set name for ConstantInt is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantInt with removeName(_) {
  ()
}

///|
pub impl Value for ConstantInt with getNameOrSlot(_) {
  None
}

///|
pub impl Constant for ConstantInt with asConstantEnum(self) {
  ConstantInt(self)
}

///|
pub impl Show for ConstantInt with output(self, logger) {
  let val_str = self.getValueRepr()
  logger.write_string("\{self.getIntegerType()} \{val_str}")
}

// ====================================================================
// ConstantFP
// ====================================================================

///|
pub struct ConstantFP {
  // Unique identifier
  uid : UInt64

  // Value type
  vty : &FPType

  // Value stored in this constant
  value : Double
} derive(Eq)

///|
fn ConstantFP::new(vty : &FPType, value : Double) -> ConstantFP {
  let uid = valueUIDAssigner.assign()
  ConstantFP::{ uid, vty, value }
}

///|
pub fn ConstantFP::getFPType(self : ConstantFP) -> &FPType {
  self.vty
}

///|
pub fn ConstantFP::getValue(self : ConstantFP) -> Double {
  self.value
}

///|
pub fn[T : Floating] ConstantFP::equals(self : ConstantFP, value : T) -> Bool {
  self.value == value.to_float64()
}

///|
pub fn[T : Floating] ConstantFP::exactlyEquals(
  self : ConstantFP,
  value : T,
) -> Bool {
  self.value.reinterpret_as_uint64() ==
  value.to_float64().reinterpret_as_uint64()
}

///| Add two ConstantFP, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantFP are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let f32_0 = ctx.getConstFloat(0.0)
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let result1 = f32_0.add(f32_1_5)
/// assert_eq(result1.getValue(), 1.5)
///
/// let f64_2_5 = ctx.getConstDouble(2.5)
/// let f64_m1_5 = ctx.getConstDouble(-1.5)
/// let result2 = f64_2_5.add(f64_m1_5)
/// assert_eq(result2.getValue(), 1.0)
/// ```
pub fn ConstantFP::add(
  self : ConstantFP,
  other : ConstantFP,
) -> ConstantFP raise LLVMValueError {
  let (lty, rty) = (self.getFPType(), other.getFPType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantFP addition: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value + other.value
  ConstantFP::new(vty, value)
}

///| Subtract two ConstantFP, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantFP are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let f32_0 = ctx.getConstFloat(0.0)
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let result1 = f32_0.sub(f32_1_5)
/// assert_eq(result1.getValue(), -1.5)
///
/// let f64_2_5 = ctx.getConstDouble(2.5)
/// let f64_m1_5 = ctx.getConstDouble(-1.5)
/// let result2 = f64_2_5.sub(f64_m1_5)
/// assert_eq(result2.getValue(), 4.0)
/// ```
pub fn ConstantFP::sub(
  self : ConstantFP,
  other : ConstantFP,
) -> ConstantFP raise LLVMValueError {
  let (lty, rty) = (self.getFPType(), other.getFPType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantFP subtraction: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value - other.value
  ConstantFP::new(vty, value)
}

///| Multiply two ConstantFP, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantFP are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let f32_0 = ctx.getConstFloat(0.0)
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let result1 = f32_0.mul(f32_1_5)
/// assert_eq(result1.getValue(), 0.0)
///
/// let f64_2_5 = ctx.getConstDouble(2.5)
/// let f64_m1_5 = ctx.getConstDouble(-1.5)
/// let result2 = f64_2_5.mul(f64_m1_5)
/// assert_eq(result2.getValue(), -3.75)
/// ```
pub fn ConstantFP::mul(
  self : ConstantFP,
  other : ConstantFP,
) -> ConstantFP raise LLVMValueError {
  let (lty, rty) = (self.getFPType(), other.getFPType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantFP multiplication: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value * other.value
  ConstantFP::new(vty, value)
}

///| Divide two ConstantFP, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForBinaryOp` if the types of two
/// ConstantFP are not the same.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let f32_0 = ctx.getConstFloat(0.0)
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let result1 = f32_0.div(f32_1_5)
/// assert_eq(result1.getValue(), 0.0)
///
/// let f64_2_5 = ctx.getConstDouble(2.5)
/// let f64_m1_5 = ctx.getConstDouble(-1.5)
/// let result2 = f64_2_5.div(f64_m1_5)
/// assert_eq(result2.getValue(), -5.0/3.0)
/// ```
pub fn ConstantFP::div(
  self : ConstantFP,
  other : ConstantFP,
) -> ConstantFP raise LLVMValueError {
  let (lty, rty) = (self.getFPType(), other.getFPType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantFP division: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let vty = lty
  let value = self.value / other.value
  ConstantFP::new(vty, value)
}

///| Compare two ConstantFP using the given predicate, useful in constant folding.
///
/// **Note**: It will raise `TypeMismatchForCmpInst` if the types of two
/// ConstantFP are not the same. Only FCMP_xxx predicates are supported.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let f32_2_5 = ctx.getConstFloat(2.5)
/// let f32_nan = ctx.getConstFloat(0.0 / 0.0)
///
/// // Test FCMP_OEQ (ordered equal)
/// inspect(f32_1_5.compare(OEQ, f32_1_5), content="i1 true")
/// inspect(f32_1_5.compare(OEQ, f32_2_5), content="i1 false")
/// inspect(f32_nan.compare(OEQ, f32_nan), content="i1 false") // NaN != NaN
///
/// // Test UEQ (unordered equal)
/// inspect(f32_1_5.compare(UEQ, f32_1_5), content="i1 true")
/// inspect(f32_nan.compare(UEQ, f32_nan), content="i1 true") // NaN == NaN in unordered
///
/// // Test OGT (ordered greater than)
/// inspect(f32_2_5.compare(OGT, f32_1_5), content="i1 true")
/// inspect(f32_1_5.compare(OGT, f32_2_5), content="i1 false")
/// inspect(f32_nan.compare(OGT, f32_1_5), content="i1 false") // NaN comparisons are false
///
/// // Test UGT (unordered greater than)
/// inspect(f32_2_5.compare(UGT, f32_1_5), content="i1 true")
/// inspect(f32_nan.compare(UGT, f32_1_5), content="i1 true") // NaN makes it unordered
///
/// // Test OLT (ordered less than)
/// inspect(f32_1_5.compare(OLT, f32_2_5), content="i1 true")
/// inspect(f32_2_5.compare(OLT, f32_1_5), content="i1 false")
///
/// // Test ORD (ordered - no NaNs)
/// inspect(f32_1_5.compare(ORD, f32_2_5), content="i1 true")
/// inspect(f32_nan.compare(ORD, f32_1_5), content="i1 false")
///
/// // Test UNO (unordered - has NaNs)
/// inspect(f32_1_5.compare(UNO, f32_2_5), content="i1 false")
/// inspect(f32_nan.compare(UNO, f32_1_5), content="i1 true")
///
/// // Test TRUE and FCMP_FALSE
/// inspect(f32_1_5.compare(TRUE, f32_2_5), content="i1 true")
/// inspect(f32_1_5.compare(FALSE, f32_2_5), content="i1 false")
///
/// // Test type mismatch
/// let f64_1_5 = ctx.getConstDouble(1.5)
/// assert_true({try? f32_1_5.compare(OEQ, f64_1_5)} is Err(_))
/// ```
pub fn ConstantFP::compare(
  self : ConstantFP,
  predicate : FloatPredicate,
  other : ConstantFP,
) -> ConstantInt raise LLVMValueError {
  let (lty, rty) = (self.getFPType(), other.getFPType())
  guard lty == rty else {
    let msg = "Type mismatch for ConstantFP comparison: \{lty} vs \{rty}"
    raise LLVMValueError(msg)
  }
  let self_val = self.value
  let other_val = other.value
  let self_is_nan = self_val.is_nan()
  let other_is_nan = other_val.is_nan()
  let is_unordered = self_is_nan || other_is_nan
  let ctx = self.getType().getContext()
  let result = match predicate {
    FALSE => false
    TRUE => true
    OEQ => not(is_unordered) && self_val == other_val
    OGT => not(is_unordered) && self_val > other_val
    OGE => not(is_unordered) && self_val >= other_val
    OLT => not(is_unordered) && self_val < other_val
    OLE => not(is_unordered) && self_val <= other_val
    ONE => not(is_unordered) && self_val != other_val
    ORD => not(is_unordered)
    UNO => is_unordered
    UEQ => is_unordered || self_val == other_val
    UGT => is_unordered || self_val > other_val
    UGE => is_unordered || self_val >= other_val
    ULT => is_unordered || self_val < other_val
    ULE => is_unordered || self_val <= other_val
    UNE => is_unordered || self_val != other_val
  }
  match result {
    true => ctx.getConstTrue()
    false => ctx.getConstFalse()
  }
}

///| Truncate this ConstantFP to a smaller floating-point type, useful in constant folding.
///
/// **Note**: It will raise `FPTruncCastInstTypeMismatch` if the source type bit width
/// is not greater than the destination type bit width.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test f64 to f32 truncation
/// let f64_1_5 = ctx.getConstDouble(1.5)
/// let f32_1_5 = f64_1_5.fptrunc(ctx.getFloatTy())
/// assert_eq(f32_1_5.getValue(), 1.5)
///
/// // Test f64 to f32 truncation with another value
/// let f64_precise = ctx.getConstDouble(2.5)
/// let f32_truncated = f64_precise.fptrunc(ctx.getFloatTy())
/// // In constant folding, the value is preserved
/// assert_eq(f32_truncated.getValue(), 2.5)
///
/// // Test error case: cannot truncate to larger type
/// let f32_val = ctx.getConstFloat(2.0)
/// assert_true({try? f32_val.fptrunc(ctx.getDoubleTy())} is Err(_))
/// ```
pub fn ConstantFP::fptrunc(
  self : ConstantFP,
  dst_ty : &FPType,
) -> ConstantFP raise LLVMValueError {
  let src_ty = self.vty
  guard src_ty.getBitWidth() > dst_ty.getBitWidth() else {
    let msg = "Floating-point truncation cast type mismatch: cannot truncate from \{src_ty} (bit width \{src_ty.getBitWidth()}) to \{dst_ty} (bit width \{dst_ty.getBitWidth()})"
    raise LLVMValueError(msg)
  }
  // For truncation, we simply use the current value (precision loss handled by Double)
  ConstantFP::new(dst_ty, self.value)
}

///| Extend this ConstantFP to a larger floating-point type, useful in constant folding.
///
/// **Note**: It will raise `FPExtCastInstTypeMismatch` if the source type bit width
/// is not less than the destination type bit width.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test f32 to f64 extension
/// let f32_1_5 = ctx.getConstFloat(1.5)
/// let f64_1_5 = f32_1_5.fpext(ctx.getDoubleTy())
/// assert_eq(f64_1_5.getValue(), 1.5)
///
/// // Test f32 to f64 extension preserves value
/// let f32_pi = ctx.getConstFloat(3.14159265)
/// let f64_pi = f32_pi.fpext(ctx.getDoubleTy())
/// assert_eq(f64_pi.getValue(), 3.14159265)
///
/// // Test error case: cannot extend to smaller type
/// let f64_val = ctx.getConstDouble(2.0)
/// assert_true({try? f64_val.fpext(ctx.getFloatTy())} is Err(_))
/// ```
pub fn ConstantFP::fpext(
  self : ConstantFP,
  dst_ty : &FPType,
) -> ConstantFP raise LLVMValueError {
  let src_ty = self.vty
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Floating-point extension cast type mismatch: cannot extend from \{src_ty} (bit width \{src_ty.getBitWidth()}) to \{dst_ty} (bit width \{dst_ty.getBitWidth()})"
    raise LLVMValueError(msg)
  }
  // For extension, we simply use the current value (no precision loss)
  ConstantFP::new(dst_ty, self.value)
}

///| Convert this ConstantFP to an unsigned integer type, useful in constant folding.
///
/// **Note**: It will raise `FPToUIInstTypeMismatch` if the source is not a floating-point type.
/// The conversion truncates towards zero (similar to C cast).
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test positive float to unsigned integer
/// let f32_3_7 = ctx.getConstFloat(3.7)
/// let i32_3 = f32_3_7.fptoui(ctx.getInt32Ty())
/// assert_eq(i32_3.getValueAsInt64(), 3)
///
/// // Test zero conversion
/// let f64_0 = ctx.getConstDouble(0.0)
/// let i64_0 = f64_0.fptoui(ctx.getInt64Ty())
/// assert_eq(i64_0.getValueAsInt64(), 0)
///
/// // Test large float to i8 (overflow behavior)
/// let f32_1000 = ctx.getConstFloat(1000.0)
/// let i8_overflow = f32_1000.fptoui(ctx.getInt8Ty())
/// // The value will be truncated to fit in i8 range (1000 in i8 is -24)
/// assert_eq(i8_overflow.getValueAsInt64(), -24)
///
/// // Test fractional part truncation
/// let f64_9_9 = ctx.getConstDouble(9.9)
/// let i32_9 = f64_9_9.fptoui(ctx.getInt32Ty())
/// assert_eq(i32_9.getValueAsInt64(), 9)
/// ```
pub fn ConstantFP::fptoui(
  self : ConstantFP,
  dst_ty : &IntegerType,
) -> ConstantInt {
  // Convert float to unsigned integer (truncate towards zero)
  let int_value = if self.value < 0.0 {
    0L // Negative values become 0 in unsigned conversion
  } else {
    self.value.to_int64()
  }
  ConstantInt::new(dst_ty, int_value)
}

///| Convert this ConstantFP to a signed integer type, useful in constant folding.
///
/// **Note**: It will raise `FPToSIInstTypeMismatch` if the source is not a floating-point type.
/// The conversion truncates towards zero (similar to C cast).
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test positive float to signed integer
/// let f32_3_7 = ctx.getConstFloat(3.7)
/// let i32_3 = f32_3_7.fptosi(ctx.getInt32Ty())
/// assert_eq(i32_3.getValueAsInt64(), 3)
///
/// // Test negative float to signed integer
/// let f64_m5_2 = ctx.getConstDouble(-5.2)
/// let i64_m5 = f64_m5_2.fptosi(ctx.getInt64Ty())
/// assert_eq(i64_m5.getValueAsInt64(), -5)
///
/// // Test zero conversion
/// let f32_0 = ctx.getConstFloat(0.0)
/// let i16_0 = f32_0.fptosi(ctx.getInt16Ty())
/// assert_eq(i16_0.getValueAsInt64(), 0)
///
/// // Test fractional part truncation
/// let f64_m9_9 = ctx.getConstDouble(-9.9)
/// let i32_m9 = f64_m9_9.fptosi(ctx.getInt32Ty())
/// assert_eq(i32_m9.getValueAsInt64(), -9)
/// ```
pub fn ConstantFP::fptosi(
  self : ConstantFP,
  dst_ty : &IntegerType,
) -> ConstantInt {
  // Convert float to signed integer (truncate towards zero)
  let int_value = self.value.to_int64()
  ConstantInt::new(dst_ty, int_value)
}

///| Bitcast this ConstantFP to another primitive type of the same bit width, useful in constant folding.
///
/// **Note**: It will raise `BitCastInstTypeMismatch` if the types have different bit widths
/// or if the cast is invalid. It will raise `BitCastOnlyAcceptPrimitiveTypes` if the source
/// is not a primitive type.
///
/// ```moonbit
/// let ctx = Context::new()
///
/// // Test f32 to i32 bitcast
/// let f32_2_0 = ctx.getConstFloat(2.0)
/// let i32_bits = f32_2_0.bitcast(ctx.getInt32Ty())
/// guard i32_bits.asConstantEnum() is ConstantInt(i32_bits)
/// assert_eq(i32_bits.getValueAsInt64(), 0x40000000) // 2.0f bit pattern
///
/// // Test f64 to i64 bitcast
/// let f64_2_0 = ctx.getConstDouble(2.0)
/// let i64_bits = f64_2_0.bitcast(ctx.getInt64Ty())
/// guard i64_bits.asConstantEnum() is ConstantInt(i64_bits)
/// assert_eq(i64_bits.getValueAsInt64(), 0x4000000000000000L) // 2.0 bit pattern
///
/// // Test f32 to f32 (same type should error)
/// let f32_val = ctx.getConstFloat(1.0)
/// assert_true({try? f32_val.bitcast(ctx.getFloatTy())} is Err(_))
///
/// // Test error case: different bit widths
/// let f64_val = ctx.getConstDouble(42.0)
/// assert_true({try? f64_val.bitcast(ctx.getInt32Ty())} is Err(_))
/// ```
pub fn ConstantFP::bitcast(
  self : ConstantFP,
  dst_ty : &PrimitiveType,
) -> &Constant raise LLVMValueError {
  let src_ty = self.getType()
  guard src_ty != dst_ty else {
    let msg = "Bitcast type mismatch: source type \{src_ty} is the same as destination type \{dst_ty}"
    raise LLVMValueError(msg)
  }
  guard src_ty.tryAsPrimitiveTypeEnum() is Some(src_ty_prim) else {
    let msg = "Bitcast only accepts primitive types: source type \{src_ty} is not a primitive type"
    raise LLVMValueError(msg)
  }
  guard src_ty_prim.getBitWidth() == dst_ty.getBitWidth() else {
    let msg = "Bitcast type mismatch: source type \{src_ty} (bit width \{src_ty_prim.getBitWidth()}) and destination type \{dst_ty} (bit width \{dst_ty.getBitWidth()}) have different bit widths"
    raise LLVMValueError(msg)
  }
  match dst_ty.asPrimitiveTypeEnum() {
    // Floating point to integer
    Int32Type(dst_ty) => {
      // For f32 to i32, reinterpret the 32-bit float as 32-bit int
      let f32_bits = self.value.to_float().reinterpret_as_uint()
      let i32_value = f32_bits.reinterpret_as_int().to_int64()
      ConstantInt::new(dst_ty, i32_value) as &Constant
    }
    Int64Type(dst_ty) => {
      // For f64 to i64, reinterpret the 64-bit double as 64-bit int
      let f64_bits = self.value.reinterpret_as_uint64()
      let i64_value = f64_bits.reinterpret_as_int64()
      ConstantInt::new(dst_ty, i64_value) as &Constant
    }
    // Floating point to floating point
    FloatType(_) | DoubleType(_) | HalfType(_) | BFloatType(_) | FP128Type(_) =>
      ConstantFP::new(dst_ty.tryAsFPType().unwrap(), self.value) as &Constant
    // Other integer types
    Int1Type(_) | Int8Type(_) | Int16Type(_) => {
      // For other integer types, we need proper bit width matching
      let msg = "Bitcast type mismatch: source type \{src_ty} and destination type \{dst_ty} have incompatible types for bitcast"
      raise LLVMValueError(msg)
    }
  }
}

///|
pub impl Value for ConstantFP with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantFP with getValueRepr(self) {
  let val_str = self.value
    .reinterpret_as_uint64()
    .to_string(radix=16)
    .to_upper()
  "0x\{val_str}"
}

///|
pub impl Value for ConstantFP with asValueEnum(self) {
  ConstantFP(self)
}

///|
pub impl Value for ConstantFP with getName(_) {
  None
}

///|
pub impl Value for ConstantFP with setName(_, _) {
  let msg = "Calling always failed function `ConstantFP::setName`. " +
    "Set name for ConstantFP is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantFP with removeName(_) {
  ()
}

///|
pub impl Value for ConstantFP with getNameOrSlot(_) {
  None
}

///|
pub impl Constant for ConstantFP with asConstantEnum(self) {
  ConstantFP(self)
}

///|
pub impl Show for ConstantFP with output(self, logger) {
  let val_str = self.getValueRepr()
  logger.write_string("\{self.getFPType()} \{val_str}")
}

// ====================================================================
// ConstantAggregateZero
// ====================================================================

//pub struct ConstantAggregateZero {
//  base : ValueBase
//} derive(Eq, Hash)
//
//fn ConstantAggregateZero::new(vty : &Type) -> ConstantAggregateZero {
//  guard vty.asTypeEnum() is (
//    ArrayType(_)
//    | StructType(_)
//    | FixedVectorType(_)
//    | ScalableVectorType(_)) else {
//    llvm_unreachable("ConstantAggregateZero::new: vty is not ArrayType, VectorType or StructType")
//  }
//  let base = ValueBase::{ vty,  users: [] }
//  ConstantAggregateZero::{ base }
//}
//
//impl Value for ConstantAggregateZero with getValueBase(self) {
//  self.base
//}
//
//impl Value for ConstantAggregateZero with asValueEnum(self) {
//  ConstantAggregateZero(self)
//}
//
//impl Show for ConstantAggregateZero with output(self, logger) {
//  logger.write_string("\{self.getType()} zeroinitializer")
//}

// ====================================================================
// ConstantPointerNull
// ====================================================================

///|
pub struct ConstantPointerNull {

  // Unique identifier
  uid : UInt64

  // Value type, must be a pointer type
  vty : PointerType
} derive(Eq)

///|
fn ConstantPointerNull::new(vty : PointerType) -> ConstantPointerNull {
  guard vty.asTypeEnum() is PointerType(_) else {
    llvm_unreachable("ConstantPointerNull::new: vty is not PointerType")
  }
  let uid = valueUIDAssigner.assign()
  ConstantPointerNull::{ uid, vty }
}

///|
pub impl Value for ConstantPointerNull with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantPointerNull with asValueEnum(self) {
  ConstantPointerNull(self)
}

///|
pub impl Value for ConstantPointerNull with getValueRepr(_) {
  "null"
}

///|
pub impl Value for ConstantPointerNull with getName(_) {
  None
}

///|
pub impl Value for ConstantPointerNull with setName(_, _) {
  let msg = "Calling always failed function `ConstantPointerNull::setName`. " +
    "Set name for ConstantPointerNull is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantPointerNull with removeName(_) {
  ()
}

///|
pub impl Value for ConstantPointerNull with getNameOrSlot(_) {
  None
}

///|
pub impl Constant for ConstantPointerNull with asConstantEnum(self) {
  ConstantPointerNull(self)
}

///|
pub impl Show for ConstantPointerNull with output(self, logger) {
  logger.write_string("\{self.getType()} null")
}

// ====================================================================
// ConstantArray
// ====================================================================

///|
pub struct ConstantArray {
  // Unique identifier
  uid : UInt64

  // Value type, must be an array type
  vty : ArrayType
  data : Either[Array[&Constant], NumberArrayEnum]
} derive(Eq)

///|
//fn ConstantArray::newConstantArray(
//  vty : &Type,
//  data : Array[&Constant]
//) -> ConstantArray {
//  guard vty.asTypeEnum() is ArrayType(arrTy) else {
//    llvm_unreachable("ConstantArray::new: vty is not ArrayType")
//  }
//  let eleCnt = arrTy.getElementCount().reinterpret_as_int()
//  guard data.length() == eleCnt else {
//    llvm_unreachable(
//      "ConstantArray::new: data length is not equal to element count",
//    )
//  }
//  let eleTy = arrTy.getElementType()
//  let consistency = data
//    .map(d => d.getType())
//    .fold(init=true, fn(acc, ty) { acc && ty == eleTy })
//  guard consistency else {
//    llvm_unreachable(
//      "ConstantArray::new: data type is not consistent with elementType",
//    )
//  }
//  let base = ValueBase::new(vty)
//  ConstantArray::{ base, data: Either::Left(data) }
//}

// TODO: use `Int8` instead of `Int` when standard library support `Int8`

///|
fn ConstantArray::newInt8Array(
  vty : ArrayType,
  data : Array[Int],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int8Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int8Type")
  }
  let data = data.map(i => Int8::from(i))
    |> Int8Array::from
    |> NumberArrayEnum::Int8Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newInt16Array(
  vty : ArrayType,
  data : Array[Int16],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int16Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int16Type")
  }
  let data = data
    |> Int16Array::from
    |> NumberArrayEnum::Int16Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newInt32Array(
  vty : ArrayType,
  data : Array[Int],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int32Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int32Type")
  }
  let data = data
    |> Int32Array::from
    |> NumberArrayEnum::Int32Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newInt64Array(
  vty : ArrayType,
  data : Array[Int64],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int64Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int64Type")
  }
  let data = data
    |> Int64Array::from
    |> NumberArrayEnum::Int64Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newUInt8Array(
  vty : ArrayType,
  data : Array[Byte],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int8Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int8Type")
  }
  let data = data.map(i => UInt8::from(i))
    |> UInt8Array::from
    |> NumberArrayEnum::UInt8Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newUInt16Array(
  vty : ArrayType,
  data : Array[UInt16],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int16Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int16Type")
  }
  let data = data
    |> UInt16Array::from
    |> NumberArrayEnum::UInt16Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newUInt32Array(
  vty : ArrayType,
  data : Array[UInt],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int32Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int32Type")
  }
  let data = data
    |> UInt32Array::from
    |> NumberArrayEnum::UInt32Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newUInt64Array(
  vty : ArrayType,
  data : Array[UInt64],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int64Type(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not Int64Type")
  }
  let data = data
    |> UInt64Array::from
    |> NumberArrayEnum::UInt64Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newFloatArray(
  vty : ArrayType,
  data : Array[Float],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is FloatType(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not FloatType")
  }
  let data = data
    |> FloatArray::from
    |> NumberArrayEnum::FloatArray
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
fn ConstantArray::newDoubleArray(
  vty : ArrayType,
  data : Array[Double],
) -> ConstantArray {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantArray::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is DoubleType(_) else {
    llvm_unreachable("ConstantArray::new: elementType is not DoubleType")
  }
  let data = data
    |> DoubleArray::from
    |> NumberArrayEnum::DoubleArray
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantArray::{ uid, vty, data }
}

///|
pub impl Value for ConstantArray with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantArray with asValueEnum(self) {
  ConstantArray(self)
}

///|
pub impl Value for ConstantArray with getValueRepr(self) {
  match self.data {
    Left(constants) => constants.map(c => c.to_string()).join(", ")
    Right(arr) =>
      match arr {
        Int8Array(i8arr) => i8arr.iter().map(i => "i8 \{i}").join(", ")
        Int16Array(i16arr) => i16arr.iter().map(i => "i16 \{i}").join(", ")
        Int32Array(i32arr) => i32arr.iter().map(i => "i32 \{i}").join(", ")
        Int64Array(i64arr) => i64arr.iter().map(i => "i64 \{i}").join(", ")
        UInt8Array(u8arr) => u8arr.iter().map(i => "i8 \{i}").join(", ")
        UInt16Array(u16arr) => u16arr.iter().map(i => "i16 \{i}").join(", ")
        UInt32Array(u32arr) => u32arr.iter().map(i => "i32 \{i}").join(", ")
        UInt64Array(u64arr) => u64arr.iter().map(i => "i64 \{i}").join(", ")
        FloatArray(farr) => {
          fn to_str(f : Float) {
            let nd = f.to_double()
            let ud = nd.reinterpret_as_uint64()
            let sd = ud.to_string(radix=16).to_upper()
            "float 0x\{sd}"
          }

          farr.iter().map(to_str).join(", ")
        }
        DoubleArray(darr) => {
          fn to_str(nd : Double) {
            let ud = nd.reinterpret_as_uint64()
            let sd = ud.to_string(radix=16).to_upper()
            "double 0x\{sd}"
          }

          darr.iter().map(to_str).join(", ")
        }
      }
  }
}

///|
pub impl Value for ConstantArray with getName(_) {
  None
}

///|
pub impl Value for ConstantArray with setName(_, _) {
  let msg = "Calling always failed function `ConstantArray::setName`. " +
    "Set name for ConstantArray is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantArray with removeName(_) {
  ()
}

///|
pub impl Value for ConstantArray with getNameOrSlot(_) {
  None
}

///|
impl Constant for ConstantArray with asConstantEnum(self) {
  ConstantArray(self)
}

///|
pub impl Show for ConstantArray with output(self, logger) {
  let data_str = self.getValueRepr()
  logger.write_string("\{self.getType()} [\{data_str}]")
}

// ====================================================================
// ConstantVector
// ====================================================================

///|
pub struct ConstantVector {
  uid : UInt64
  vty : FixedVectorType
  data : Either[Array[&Constant], NumberArrayEnum]
} derive(Eq)

///|
//fn ConstantVector::newConstantVector(
//  vty : &Type,
//  data : Array[&Constant]
//) -> ConstantVector {
//  guard vty.asTypeEnum() is FixedVectorType(arrTy) else {
//    llvm_unreachable("ConstantVector::new: vty is not FixedVectorType")
//  }
//  let eleCnt = arrTy.getElementCount().reinterpret_as_int()
//  guard data.length() == eleCnt else {
//    llvm_unreachable(
//      "ConstantVector::new: data length is not equal to element count",
//    )
//  }
//  let eleTy = arrTy.getElementType()
//  let consistency = data
//    .map(d => d.getType())
//    .fold(init=true, fn(acc, ty) { acc && ty == eleTy })
//  guard consistency else {
//    llvm_unreachable(
//      "ConstantVector::new: data type is not consistent with elementType",
//    )
//  }
//  let base = ValueBase::new(vty)
//  ConstantVector::{ base, data: Either::Left(data) }
//}

// TODO: use `Int8` instead of `Int` when standard library support `Int8`

///|
fn ConstantVector::newInt8Vector(
  vty : FixedVectorType,
  data : Array[Int],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int8Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int8Type")
  }
  let data = data.map(i => Int8::from(i))
    |> Int8Array::from
    |> NumberArrayEnum::Int8Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newInt16Vector(
  vty : FixedVectorType,
  data : Array[Int16],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int16Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int16Type")
  }
  let data = data
    |> Int16Array::from
    |> NumberArrayEnum::Int16Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newInt32Vector(
  vty : FixedVectorType,
  data : Array[Int],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int32Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int32Type")
  }
  let data = data
    |> Int32Array::from
    |> NumberArrayEnum::Int32Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newInt64Vector(
  vty : FixedVectorType,
  data : Array[Int64],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int64Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int64Type")
  }
  let data = data
    |> Int64Array::from
    |> NumberArrayEnum::Int64Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newUInt8Vector(
  vty : FixedVectorType,
  data : Array[Byte],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int8Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int8Type")
  }
  let data = data.map(i => UInt8::from(i))
    |> UInt8Array::from
    |> NumberArrayEnum::UInt8Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newUInt16Vector(
  vty : FixedVectorType,
  data : Array[UInt16],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int16Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int16Type")
  }
  let data = data
    |> UInt16Array::from
    |> NumberArrayEnum::UInt16Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newUInt32Vector(
  vty : FixedVectorType,
  data : Array[UInt],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int32Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int32Type")
  }
  let data = data
    |> UInt32Array::from
    |> NumberArrayEnum::UInt32Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newUInt64Vector(
  vty : FixedVectorType,
  data : Array[UInt64],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is Int64Type(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not Int64Type")
  }
  let data = data
    |> UInt64Array::from
    |> NumberArrayEnum::UInt64Array
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newFloatVector(
  vty : FixedVectorType,
  data : Array[Float],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is FloatType(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not FloatType")
  }
  let data = data
    |> FloatArray::from
    |> NumberArrayEnum::FloatArray
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
fn ConstantVector::newDoubleVector(
  vty : FixedVectorType,
  data : Array[Double],
) -> ConstantVector {
  let eleCnt = vty.getElementCount().reinterpret_as_int()
  guard data.length() == eleCnt else {
    llvm_unreachable(
      "ConstantVector::new: data length is not equal to element count",
    )
  }
  guard vty.elementType.asTypeEnum() is DoubleType(_) else {
    llvm_unreachable("ConstantVector::new: elementType is not DoubleType")
  }
  let data = data
    |> DoubleArray::from
    |> NumberArrayEnum::DoubleArray
    |> Either::Right
  let uid = valueUIDAssigner.assign()
  ConstantVector::{ uid, vty, data }
}

///|
pub impl Value for ConstantVector with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantVector with asValueEnum(self) {
  ConstantVector(self)
}

///|
pub impl Value for ConstantVector with getValueRepr(self) {
  match self.data {
    Left(constants) => constants.map(c => c.to_string()).join(", ")
    Right(arr) =>
      match arr {
        Int8Array(i8arr) => i8arr.iter().map(i => "i8 \{i}").join(", ")
        Int16Array(i16arr) => i16arr.iter().map(i => "i16 \{i}").join(", ")
        Int32Array(i32arr) => i32arr.iter().map(i => "i32 \{i}").join(", ")
        Int64Array(i64arr) => i64arr.iter().map(i => "i64 \{i}").join(", ")
        UInt8Array(u8arr) => u8arr.iter().map(i => "i8 \{i}").join(", ")
        UInt16Array(u16arr) => u16arr.iter().map(i => "i16 \{i}").join(", ")
        UInt32Array(u32arr) => u32arr.iter().map(i => "i32 \{i}").join(", ")
        UInt64Array(u64arr) => u64arr.iter().map(i => "i64 \{i}").join(", ")
        FloatArray(farr) => {
          fn to_str(f : Float) {
            let nd = f.to_double()
            let ud = nd.reinterpret_as_uint64()
            let sd = ud.to_string(radix=16).to_upper()
            "float 0x\{sd}"
          }

          farr.iter().map(to_str).join(", ")
        }
        DoubleArray(darr) => {
          fn to_str(nd : Double) {
            let ud = nd.reinterpret_as_uint64()
            let sd = ud.to_string(radix=16).to_upper()
            "double 0x\{sd}"
          }

          darr.iter().map(to_str).join(", ")
        }
      }
  }
}

///|
pub impl Value for ConstantVector with getName(_) {
  None
}

///|
pub impl Value for ConstantVector with setName(_, _) {
  let msg = "Calling always failed function `ConstantVector::setName`. " +
    "Set name for ConstantVector is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantVector with removeName(_) {
  ()
}

///|
pub impl Value for ConstantVector with getNameOrSlot(_) {
  None
}

///|
pub impl Constant for ConstantVector with asConstantEnum(self) {
  ConstantVector(self)
}

///|
pub impl Show for ConstantVector with output(self, logger) {
  let data_str = self.getValueRepr()
  logger.write_string("\{self.getType()} <\{data_str}>")
}

// ====================================================================
// ConstantStruct
// ====================================================================

///| ConstantStruct
///
/// Represents a constant struct value in LLVM IR.
///
/// ```moonbit
/// let ctx = Context::new()
/// let i32ty = ctx.getInt32Ty()
/// let f32ty = ctx.getFloatTy()
/// let sty = ctx.getStructType([i32ty, f32ty])
/// 
/// let i32_val = ctx.getConstInt32(42)
/// let f32_val = ctx.getConstFloat(3.14)
/// let struct_val = ConstantStruct::new(sty, [i32_val, f32_val])
///
/// inspect(struct_val, content="{ i32, float } { i32 42, float 0x40091EB851EB851F }")
/// ```
pub struct ConstantStruct {
  // Unique identifier
  uid : UInt64

  // Value type, must be a struct type
  vty : StructType
  elements : Array[&Constant]
} derive(Eq)

///|
pub fn ConstantStruct::new(
  vty : StructType,
  elements : Array[&Constant],
) -> ConstantStruct {
  guard vty.asTypeEnum() is StructType(sty) else {
    llvm_unreachable("ConstantStruct::new: vty is not StructType")
  }
  let struct_elements = sty.elements()
  guard elements.length() == struct_elements.length() else {
    llvm_unreachable("ConstantStruct::new: elements length mismatch")
  }
  for i = 0; i < elements.length(); i = i + 1 {
    guard elements[i].getType() == struct_elements[i] else {
      llvm_unreachable(
        "ConstantStruct::new: element type mismatch at index \{i}",
      )
    }
  }
  let uid = valueUIDAssigner.assign()
  ConstantStruct::{ uid, vty, elements: elements.copy() }
}

///| Get the elements of this constant struct.
pub fn ConstantStruct::getElements(self : ConstantStruct) -> Array[&Constant] {
  self.elements.copy()
}

///| Get the element at the specified index.
pub fn ConstantStruct::getElement(
  self : ConstantStruct,
  index : Int,
) -> &Constant? {
  if index >= 0 && index < self.elements.length() {
    Some(self.elements[index])
  } else {
    None
  }
}

///| Extract a value from this constant struct using a sequence of indices.
///
/// This is equivalent to LLVM's `extractvalue` instruction for constant structs.
/// The indices specify a path through nested aggregate types.
///
/// ```moonbit
/// let ctx = Context::new()
/// let i32ty = ctx.getInt32Ty()
/// let f32ty = ctx.getFloatTy()
/// let sty = ctx.getStructType([i32ty, f32ty])
/// 
/// let i32_val = ctx.getConstInt32(42)
/// let f32_val = ctx.getConstFloat(3.14)
/// let struct_val = ConstantStruct::new(sty, [i32_val, f32_val])
///
/// inspect(struct_val.extractValue([0]), content="Some(i32 42)")
/// inspect(struct_val.extractValue([1]), content="Some(float 0x40091EB851EB851F)")
/// inspect(struct_val.extractValue([2]), content="None") // Out of bounds
/// ```
pub fn ConstantStruct::extractValue(
  self : ConstantStruct,
  indices : ArrayView[Int],
) -> &Constant? {
  guard indices.length() > 0 else { return None }
  let first_index = indices[0]
  guard first_index >= 0 && first_index < self.elements.length() else {
    return None
  }
  let element = self.elements[first_index]
  if indices.length() == 1 {
    // Base case: return the element
    Some(element)
  } else {
    // Recursive case: extract from the nested aggregate
    match element.asConstantEnum() {
      ConstantStruct(nested_struct) => nested_struct.extractValue(indices[1:])
      _ => None // For now, only support nested structs
    }
  }
}

///| Insert a value into this constant struct using a sequence of indices.
///
/// This is equivalent to LLVM's `insertvalue` instruction for constant structs.
/// Returns a new ConstantStruct with the value inserted at the specified path.
///
/// ```moonbit
/// let ctx = Context::new()
/// let i32ty = ctx.getInt32Ty()
/// let f32ty = ctx.getFloatTy()
/// let sty = ctx.getStructType([i32ty, f32ty])
/// 
/// let i32_val = ctx.getConstInt32(42)
/// let f32_val = ctx.getConstFloat(3.14)
/// let struct_val = ConstantStruct::new(sty, [i32_val, f32_val])
/// 
/// // Insert new value at index 0
/// let new_i32_val = ctx.getConstInt32(100)
/// guard struct_val.insertValue([0], new_i32_val) is Some(new_struct)
/// 
/// // Verify the new value was inserted
/// assert_true(new_struct.extractValue([0]) is Some(_))
/// ```
/// // TODO: not correct
pub fn ConstantStruct::insertValue(
  self : ConstantStruct,
  indices : ArrayView[Int],
  value : &Constant,
) -> ConstantStruct? {
  let first_index = indices[0]
  guard first_index >= 0 && first_index < self.elements.length() else {
    return None
  }
  if indices.length() == 1 {
    // Base case: replace the element at this index
    guard self.getType().asTypeEnum() is StructType(sty) else { return None }
    guard value.getType() == sty.elements()[first_index] else { return None }
    let new_elements = self.elements.copy()
    new_elements[first_index] = value
    Some(ConstantStruct::new(self.vty, new_elements))
  } else {
    // Recursive case: insert into the nested aggregate
    let element = self.elements[first_index]
    let new_element = match element.asConstantEnum() {
      ConstantStruct(nested_struct) =>
        match nested_struct.insertValue(indices[1:], value) {
          Some(new_nested) => Some((new_nested : &Constant))
          None => None
        }
      _ => None // For now, only support nested structs
    }
    match new_element {
      Some(elem) => {
        let new_elements = self.elements.copy()
        new_elements[first_index] = elem
        Some(ConstantStruct::new(self.vty, new_elements))
      }
      None => None
    }
  }
}

///|
pub impl Value for ConstantStruct with getValueBase(self) {
  ValueBase::{ uid: self.uid, vty: self.vty, users: [] }
}

///|
pub impl Value for ConstantStruct with asValueEnum(self) {
  ConstantStruct(self)
}

///|
pub impl Value for ConstantStruct with getValueRepr(self) {
  self.elements.map(c => c.to_string()).join(", ")
}

///|
pub impl Value for ConstantStruct with getName(_) {
  None
}

///|
pub impl Value for ConstantStruct with setName(_, _) {
  let msg = "Calling always failed function `ConstantStruct::setName`. " +
    "Set name for ConstantStruct is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for ConstantStruct with removeName(_) {
  ()
}

///|
pub impl Value for ConstantStruct with getNameOrSlot(_) {
  None
}

///|
pub impl Constant for ConstantStruct with asConstantEnum(self) {
  ConstantStruct(self)
}

///|
pub impl Show for ConstantStruct with output(self, logger) {
  let data_str = self.getValueRepr()
  logger.write_string("\{self.getType()} { \{data_str} }")
}
